'''
Author:CherryXuan
Email:shenzexuan1994@foxmail.com
Wechat:cherry19940614

File:MeterReader.py
Name:仪表识别类
Version:v0.0.1
Date:2019/6/14 12:30
'''
import os
import cv2
import numpy as np
import math
import matplotlib.pyplot as plt

class METER(object):
    def __init__(self,path):
        self.path = path

    # deleted!  读取图片
    def readData(self):
        imgs_path = []
        for filename in os.listdir(self.path):
            if filename.endswith('.jpg'):
                filename = self.path + '/' + filename
                # 图片归一化处理
                #res = cv2.resize(img, None, fx=0.5, fy=0.5, interpolation=cv2.INTER_LINEAR)  # 按照比例缩放，如x,y轴均缩小一倍
                imgs_path.append(filename)
        return imgs_path

    def find_hand_edge(self,edges):
        ##从最苛刻条件 的开始, 苛刻条件的 检测出的线段 是指针的概率就最大了。
        for threshold in range(80, 20, -2):
            ##参数　阈值！
            lines = cv2.HoughLinesP(edges, 1, np.pi / 180, threshold=threshold, minLineLength=30, maxLineGap=5)
            if lines is not None:
                return lines[0]
                #return lines[0][0]  ##第一条线段，  多条情况　挑选？
        pass
    # 图片归一化处理
    def normalized_picture(self):
        img = cv2.imread(self.path)
        #nor = cv2.resize(img, None, fx=0.5, fy=0.5, interpolation=cv2.INTER_LINEAR)  # 按照比例缩放，如x,y轴均缩小一倍
        ##nor = cv2.resize(img, None, fx=0.5, fy=0.5, interpolation=cv2.INTER_LINEAR)  # 按照比例缩放，如x,y轴均缩小一倍
        rows, cols, ch = img.shape
        self.mb_row = rows
        self.mb_col = cols
        cv2.imshow('Normalized picture',img)
        return img

    # 颜色空间转换：灰度化
    def color_conversion(self,img):
        img_gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)  # 转换为灰度图
        cv2.imshow('Graying pictures', img_gray)
        return img_gray

    # 中值滤波去噪
    def median_filter(self,img):
        median = cv2.medianBlur(img, 1)  # 中值滤波
        cv2.imshow('Median filter', median)
        return median

    # 双边滤波去噪
    def bilateral_filter(self,img):
        bilateral = cv2.bilateralFilter(img, 9, 50, 50)
        cv2.imshow('Bilateral filter', bilateral)
        return bilateral

    # 高斯滤波去噪
    def gaussian_filter(self,img):
        gaussian = cv2.GaussianBlur(img, (3, 3), 0)
        cv2.imshow('Gaussian filter', gaussian)
        return gaussian

    # 图像二值化
    def binary_image(self,img):
        # 应用5种不同的阈值方法
        # ret, th1 = cv2.threshold(img_gray, 127, 255, cv2.THRESH_BINARY)
        # ret, th2 = cv2.threshold(img_gray, 127, 255, cv2.THRESH_BINARY_INV)
        # ret, th3 = cv2.threshold(img_gray, 127, 255, cv2.THRESH_TRUNC)
        # ret, th4 = cv2.threshold(img_gray, 127, 255, cv2.THRESH_TOZERO)
        # ret, th5 = cv2.threshold(img_gray, 127, 255, cv2.THRESH_TOZERO_INV)
        # titles = ['Gray', 'BINARY', 'BINARY_INV', 'TRUNC', 'TOZERO', 'TOZERO_INV']
        # images = [img_gray, th1, th2, th3, th4, th5]
        # 使用Matplotlib显示
        # for i in range(6):
        #     plt.subplot(2, 3, i + 1)
        #     plt.imshow(images[i], 'gray')
        #     plt.title(titles[i], fontsize=8)
        #     plt.xticks([]), plt.yticks([])  # 隐藏坐标轴
        # plt.show()

        # Otsu阈值
        _, th = cv2.threshold(img, 0, 255, cv2.THRESH_TOZERO + cv2.THRESH_OTSU)
        cv2.imshow('Binary image', th)
        return th

    # 边缘检测
    def candy_image(self,img):
        ##edges = cv2.Canny(img, 60, 143, apertureSize=3)
        edges = cv2.Canny(img, 100, 200, apertureSize=3)
        cv2.imshow('canny', edges)
        return edges

    # 开运算：先腐蚀后膨胀
    def open_operation(self,img):
        # 定义结构元素
        kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (5, 5))  # 矩形结构
        # kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (5, 5))   # 椭圆结构
        # kernel = cv2.getStructuringElement(cv2.MORPH_CROSS, (5, 5))     # 十字形结构
        opening = cv2.morphologyEx(img, cv2.MORPH_OPEN, kernel)  # 开运算
        cv2.imshow('Open operation', opening)
        return opening

    # 霍夫圆变换：检测表盘
    def detect_circles(self,gray,img):
        circles = cv2.HoughCircles(gray, cv2.HOUGH_GRADIENT, 1, 40, param1=100, param2=45,
                                   minRadius=int(self.mb_col * 0.38))
        #circles = cv2.HoughCircles(gray, cv2.HOUGH_GRADIENT, 1, 100, param2=200, minRadius=int(self.mb_col*0.15))
        ##circles = cv2.HoughCircles(gray, cv2.HOUGH_GRADIENT, 1, 100, param2=150, minRadius=int(self.mb_col * 0.15))
        if circles is None:
            print("竟然没有找到圆形表盘", circles)
        circles = np.uint16(np.around(circles))  # 把circles包含的圆心和半径的值变成整数
        print("找到圆形表盘" , circles)

        cir = img.copy()

        for i in circles[0, :]:
            cv2.circle(cir, (i[0], i[1]), i[2], (0, 255, 0), 2, cv2.LINE_AA)  # 画圆
            cv2.circle(cir, (i[0], i[1]), 2, (0, 255, 0), 2, cv2.LINE_AA)  # 画圆心
            self.circle = (i[0], i[1])

        cv2.imshow("circles", cir)
        cv2.imwrite("circles.jpg", cir)
        return cir

    # 霍夫直线变换：检测指针
    def detect_pointer(self,cir, orgimg):
        img = cv2.GaussianBlur(cir, (3, 3), 0)
        edges = cv2.Canny(img, 100, 200, apertureSize=3)
        #edges = cv2.Canny(img, 50, 150, apertureSize=3)
        cv2.imshow("edges_pointer", edges)
        #同样都是lines输出类型竟然都不一样？； 只检测出了大指针的两个边锋线段。
        #lines = cv2.HoughLinesP(edges, 1, np.pi / 180, 55, None, minLineLength=30)
        #参数threshold=60=；  检测出来直线线段很多。
        lines = cv2.HoughLines(edges, 1, np.pi / 180, 98)

        result = orgimg.copy()
        if lines is not None:
            for i in range(0, len(lines)):
                rho = lines[i][0][0]
                theta = lines[i][0][1]
                a = np.cos(theta)
                b = np.sin(theta)
                x0 = a * rho
                y0 = b * rho
                pt1 = (int(x0 + 1000 * (-b)), int(y0 + 1000 * (a)))
                pt2 = (int(x0 - 1000 * (-b)), int(y0 - 1000 * (a)))
                ##这个线条是长长地 贯穿的，
                cv2.line(result, pt1, pt2, (0, 0, 255), 3, cv2.LINE_AA)
        cv2.imshow('Result', result)
        ##lines数量很多； lines输出都是长长地贯穿的直线条。
        for line in lines[0]:
            rho = line[0]  # 第一个元素是距离rho
            theta = line[1]  # 第二个元素是角度theta
            rtheta = theta * (180 / np.pi)
            print('θ1:', rtheta)
            if (theta < (np.pi / 4.)) or (theta > (3. * np.pi / 4.0)):  # 垂直直线
                # 该直线与第一行的交点
                pt1 = (int(rho / np.cos(theta)), 0)
                # 该直线与最后一行的焦点
                pt2 = (int((rho - result.shape[0] * np.sin(theta)) / np.cos(theta)), result.shape[0])
                a = int(
                    int(int(rho / np.cos(theta)) + int((rho - result.shape[0] * np.sin(theta)) / np.cos(theta))) / 2)
                b = int(result.shape[0] / 2)
                pt3 = (a, b)
                pt4 = (int(int(int(rho / np.cos(theta)) + a) / 2), int(b / 2))
                # 绘制一条白线
                cv2.putText(result, 'theta1={}'.format(int(rtheta)), pt4, cv2.FONT_HERSHEY_COMPLEX, 0.5, (0, 0, 255), 1)
                cv2.line(result, pt3, pt4, (0, 0, 255), 2, cv2.LINE_AA)
            else:  # 水平直线
                # 该直线与第一列的交点
                pt1 = (0, int(rho / np.sin(theta)))
                # 该直线与最后一列的交点
                pt2 = (result.shape[1], int((rho - result.shape[1] * np.cos(theta)) / np.sin(theta)))
                a = int(
                    int(int(rho / np.cos(theta)) + int((rho - result.shape[0] * np.sin(theta)) / np.cos(theta))) / 2)
                b = int(result.shape[0] / 2)
                pt3 = (a, b)
                pt4 = (int(int(int(rho / np.cos(theta)) + a) / 2), int(b / 2))
                # 绘制一条直线
                cv2.line(result, pt3, pt4, (0, 0, 255), 2, cv2.LINE_AA)

        #for line in lines[2]:
        for line in lines[2]:
            rho = line[0]  # 第一个元素是距离rho
            theta = line[1]  # 第二个元素是角度theta
            rtheta = theta * (180 / np.pi)
            print('θ2:', - rtheta - 90)
            if (theta < (np.pi / 4.)) or (theta > (3. * np.pi / 4.0)):  # 垂直直线
                # 该直线与第一行的交点
                pt1 = (int(rho / np.cos(theta)), 0)
                # 该直线与最后一行的焦点
                pt2 = (int((rho - result.shape[0] * np.sin(theta)) / np.cos(theta)), result.shape[0])
                a = int(
                    int(int(rho / np.cos(theta)) + int((rho - result.shape[0] * np.sin(theta)) / np.cos(theta))) / 2)
                b = int(result.shape[0] / 2)
                pt3 = (a, b)
                pt4 = (int(int(int((rho - result.shape[0] * np.sin(theta)) / np.cos(theta)) + a) / 2),
                       int(int(int(b + result.shape[0]) / 2)))
                # 绘制一条白线
                cv2.putText(result, 'theta2={}'.format(int(- rtheta - 90)), pt4, cv2.FONT_HERSHEY_COMPLEX, 0.5,
                            (0, 0, 255), 1)
                cv2.line(result, pt3, pt4, (255, 0, 0), 2, cv2.LINE_AA)
            else:  # 水平直线
                # 该直线与第一列的交点
                pt1 = (0, int(rho / np.sin(theta)))
                # 该直线与最后一列的交点
                pt2 = (result.shape[1], int((rho - result.shape[1] * np.cos(theta)) / np.sin(theta)))
                a = int(
                    int(int(rho / np.cos(theta)) + int((rho - result.shape[0] * np.sin(theta)) / np.cos(theta))) / 2)
                b = int(result.shape[0] / 2)
                pt3 = (a, b)
                pt4 = (int(int(int((rho - result.shape[0] * np.sin(theta)) / np.cos(theta)) + a) / 2),
                       int(int(int(b + result.shape[0]) / 2)))
                # 绘制一条直线
                cv2.line(result, pt3, pt4, (255, 0, 0), 2, cv2.LINE_AA)

        # cv2.imshow('Canny', edges)
        cv2.imshow('Result', result)

        return result
    def iden_pic(self):
        ## image 是对象类？
        image = METER(self.path)
        nor = image.normalized_picture()
        ## 转换为灰度图
        gray = image.color_conversion(nor)
        ## 图像二值化
        binary = image.binary_image(gray)
        ### 中值滤波去噪
        median = image.median_filter(binary)
        #bilateral = image.bilateral_filter(median)
        gaussian = image.gaussian_filter(median)
        candy = image.candy_image(median)
        cir = image.detect_circles(gray,nor)   ##nor==原图

        pointer = image.detect_pointer_P(median, cir)   ##cir==原图

        #不如 detect_pointer_P 好用
        #pointer = image.detect_pointer(median, cir)
        #pointer = image.detect_pointer(cir / nor)
        cv2.waitKey(0)
        cv2.destroyAllWindows()



    # 霍夫直线变换：检测指针
    def detect_pointer_P(self,cir, orgimg):
        img = cv2.GaussianBlur(cir, (3, 3), 0)
        edges = cv2.Canny(img, 100, 200, apertureSize=3)
        #edges = cv2.Canny(img, 50, 150, apertureSize=3)
        cv2.imshow("edges_pointer", edges)
        threshold = int(self.mb_col*0.1*0.9)
        #threshold和minlength差不多， 若threshold越大检测出来的直线段就越少。
        #同样都是lines输出类型竟然都不一样？； 只检测出了大指针的两个边锋线段。 , minLineLength=20
        lines = cv2.HoughLinesP(edges, 1, np.pi / 180, threshold, None, minLineLength=int(self.mb_col*0.1))

        #参数threshold60=threshold:判断直线点数的阈值。；minLineLength：线段长度阈。 maxLineGap:被叠加线段上两点之间的最大允许点数阈值。

        #lines = cv2.HoughLines(edges, 1, np.pi / 180, 60)

        ## lines 找不到， 更改办法！！

        # [表指针的直线段] 查找最佳的 可能指针线段。
        ##只有一条线；
        lines = self.find_hand_edge(edges)
        ##参数没调好 就找不到；   edges-4.jpg 基本没剩下什么轮廓了。
        if lines is None:
            return None  ##没找到指针

        result = orgimg.copy()
        # linesP = lines
        linesP= [lines]
        ##计算两 条线段之间的夹角
        clx,cly = self.circle

        if linesP is not None:
            for i in range(0, len(linesP)):
                l = linesP[i][0]
                cv2.line(result, (l[0], l[1]), (l[2], l[3]), (5, 255, 21), 1, cv2.LINE_AA)
                L1 = Line(Point(clx, cly), Point(clx + 50, cly))
                L2 = Line(Point(l[0], l[1]), Point(l[2], l[3]))
                res = GetAngle(L1, L2)
                ##可能 有多个输出的
                print("计算指针相对角度=", res)
        else:
            print("竟然一条直线段都没发现？！", linesP)
        cv2.imshow('Result', result)

        pass
        return result

        # cv2.imshow('Canny', edges)
        cv2.imshow('Result', result)
        return result

##HoughLines( )函数 和 HoughLinesP()函数 https://blog.csdn.net/weixin_41906949/article/details/85049238?utm_medium=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-1.compare&depth_1-utm_source=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-1.compare





class Point:
    """
    2D坐标点
    """
    def __init__(self, x, y):
        self.X = x
        self.Y = y


class Line:
    def __init__(self, point1, point2):
        """
        初始化包含两个端点
        :param point1:
        :param point2:
        """
        self.Point1 = point1
        self.Point2 = point2


def GetAngle(line1, line2):
    """
    计算两条线段之间的夹角
    :param line1:
    :param line2:
    :return:
    """
    dx1 = line1.Point1.X - line1.Point2.X
    dy1 = line1.Point1.Y - line1.Point2.Y
    dx2 = line2.Point1.X - line2.Point2.X
    dy2 = line2.Point1.Y - line2.Point2.Y
    angle1 = math.atan2(dy1, dx1)
    angle1 = angle1 * 180.0 / math.pi
    # print(angle1)
    angle2 = math.atan2(dy2, dx2)
    angle2 = angle2 * 180.0 / math.pi
    # print(angle2)
    if angle1 * angle2 >= 0:
        insideAngle = abs(angle1 - angle2)
    else:
        insideAngle = abs(angle1) + abs(angle2)
        if insideAngle > 180.0:
            insideAngle = 360.0 - insideAngle
    insideAngle = insideAngle % 180.0
    return insideAngle

